.. _глава_80_start:

Примеси (Mixins)
================

.. index::
    Mixins
    примеси

Mixins предназначены для добавления сгенерированного кода в исходный код. Смешиваемый код
может быть сгенерирован в виде экземпляра шаблона или строки.
Код может быть вставлен в программу и как импорт строки.


mixins шаблонов
----------------

В главах :ref:`Шаблоны<глава_64_start>` и :ref:`Больше шаблонов<глава_78_start>`  мы видели.
что шаблоны определяют код как образец, а компилятор генерирует фактические
экземпляров на основе этого шаблона. Шаблоны могут генерировать функции, структуры, объединения,
классы, интерфейсы и любой другой легальный D-код.
mixins шаблонов вставляют инстансы шаблонов в код с помощью ключевого слова mixin:

.. code-block:: d

    mixin a_template!(template_parameters)

Как мы увидим в примере ниже, ключевое слово mixin используется в определениях
шаблонов-миксинов.
Инстанцирование шаблона для определенного набора параметров шаблона
вставляется в исходный код прямо в том месте, где появляется ключевое слово mixin.
Например, пусть у нас есть шаблон, определяющий как массив ребер, так и пару
функций, которые работают с этими гранями:

.. code-block:: d

    mixin template EdgeArrayFeature(T, size_t count)
    {
        T[count] edges;
        void setEdge(size_t index, T edge)
        {
            edges[index] = edge;
        }

        void printEdges()
        {
            writeln("The edges:");
            foreach (i, edge; edges)
            {
                writef("%s:%s ", i, edge);
            }
            writeln();
        }
    }


В этом шаблоне тип и количество элементов массива могут быть заданы произвольно. На сайте
инстанция этого шаблона для int и 2 будет смешана со посредством следующего синтаксиса:

.. code-block:: d

    mixin EdgeArrayFeature!(int, 2);

Например, в приведенном выше mixin можно вставить двухэлементный массив int и две
функции, генерируемые шаблоном, прямо в определение struct:

.. code-block:: d

    struct Line
    {
        mixin EdgeArrayFeature!(int, 2);
    }

В результате Line определяет массив-член и две функции-члена:

.. code-block:: d

    import std.stdio;

    void main()
    {
        auto line = Line();
        line.setEdge(0, 100);
        line.setEdge(1, 200);
        line.printEdges();
    }

.. code-block:: console

    The edges:
    0:100 1:200

Другая инициализация того же шаблона может быть использована, например, внутри функции:

.. code-block:: d

    struct Point
    {
        int x;
        int y;
    }

    void main()
    {
        mixin EdgeArrayFeature!(Point, 5);
        setEdge(3, Point(3, 3));
        printEdges();
    }


Этот миксин вставляет массив и две локальные функции внутрь main().

Вывод:

.. code-block:: console

    The edges:
    0:Point(0, 0) 1:Point(0, 0) 2:Point(0, 0) 3:Point(3, 3) 4:Point(0, 0)


**Миксины шаблонов должны использовать локальный импорт**


Смешивание инстанций шаблонов как таковых может привести к проблемам с модулями
которые использует сам шаблон: Эти модули могут быть недоступны на
на стороне миксина.
Рассмотрим следующий модуль с именем a. Естественно, он должен импортировать
модуль std.string, который он использует:

.. code-block:: d

    module a;

    import std.string; // ← wrong place

    mixin template A(T)
    {
        string a()
        {
            T[] array;
            // ...
            return format("%(%s, %)", array);
        }
    }

Однако, если std.string не импортируется в месте фактического размещения миксина, то
компилятор не сможет найти определение format() в этой точке. Давайте
рассмотрим следующую программу, которая импортирует a и пытается подмешать A!int из
этого модуля:

.. code-block:: d

    import a;

    void main()
    {
        mixin A!int; // ← ошибка компиляции
    }

.. code-block:: console

    Error: undefined identifier format
    Error: mixin deneme.main.A!int error instantiating

По этой причине модули, которые используют шаблонные mixins, должны быть импортированы в локальные
диапазонах:

.. code-block:: d

    module a;
    mixin template A(T)
    {
        string a()
        {
            import std.string; // ← right place
            T[] array;
            // ...
            return format("%(%s, %)", array);
        }
    }

Пока она находится внутри определения шаблона, приведенная выше директива import может находиться
вне функции a().

**Определение типа, который смешивается**

Иногда mixin может потребоваться идентифицировать тип, который в него подмешивается. Эта
информация доступна через параметры этого шаблона, как мы уже видели в главе
Другие шаблоны (:ref:`Дополнительная информация о шаблонах<глава_78_start>`):


.. code-block:: d


    mixin template MyMixin(T)
    {
        void foo(this MixingType)()
        {
            import std.stdio;

            writefln("The actual type that is mixing in: %s",
                MixingType.stringof);
        }
    }

    struct MyStruct
    {
        mixin MyMixin!(int);
    }

    void main()
    {
        auto a = MyStruct();
        a.foo();
    }

Вывод программы показывает, что реальный тип доступен внутри как MyStruct:

.. code-block:: console

    The actual type that is mixing in: MyStruct

Миксины строк
-------------

Еще одна мощная особенность D - возможность вставлять код в виде строки, если
если эта строка известна во время компиляции. Синтаксис строковых миксинов требует использования
круглых скобок:

.. code-block:: d

    mixin (compile_time_generated_string)

Например, программа hello world может быть написана с помощью mixin

.. code-block:: d

    import std.stdio;

    void main()
    {
        mixin (`writeln("Hello, World!");`);
    }

Строка вставляется как код, и программа выдает следующий результат:

.. code-block:: console

    Hello, World

Мы можем пойти дальше и вставить всю программу как строковый миксин:

.. code-block:: d

    mixin
    (
        `import std.stdio; void main() { writeln("Hello, World!"); }`
    );

Очевидно, что в этих примерах нет необходимости в миксинах, поскольку строки можно было бы
можно было бы написать и как код.
Сила строковых миксинов заключается в том, что код может быть сгенерирован
во время компиляции. Следующий пример использует преимущества CTFE для генерации
операторов во время компиляции:

.. code-block:: d

    import std.stdio;

    string printStatement(string message)
    {
        return `writeln("` ~ message ~ `");`;
    }

    void main()
    {
        mixin(printStatement("Hello, World!"));
        mixin(printStatement("Hi, World!"));
    }


Вывод:

.. code-block:: d

    Hello, World!
    Hi, World

Обратите внимание, что выражения "writeln" не выполняются внутри printStatement().
Скорее, printStatement() генерирует код, включающий выражения writeln()
которые выполняются внутри main(). Сгенерированный код эквивалентен следующему
следующему:

.. code-block:: d

    import std.stdio;

    void main()
    {
        writeln("Hello, World!");
        writeln("Hi, World!");
    }

**Несколько аргументов mixin**

До тех пор, пока все они известны во время компиляции, mixin может принимать несколько аргументов
и автоматически конкатенирует их строковые представления:

.. code-block:: d

    mixin ("const a = ", int.sizeof, ";");

Это может быть удобнее, чем использование, например, выражения формата:

.. code-block:: d

    mixin (format!"const a = %s;"(int.sizeof)); // Same as above


**Отладка строковых миксинов**

Поскольку сгенерированный код не всегда виден в исходном коде целиком, бывает трудно
трудно определить причины ошибок компиляции с использованием выражений-mixins. Чтобы помочь
с отладкой строковых mixin'ов, существует ключ компилятора dmd -mixin, который
записывает весь смешанный код в указанный файл.
Рассмотрим следующую программу, в которой обнаружена синтаксическая ошибка в коде, который
смешивается. Из ошибки компилятора не очевидно, что синтаксической ошибкой является
отсутствие точки с запятой в конце определения члена struct:

.. code-block:: d

    string makeStruct(string name, string member)
    {
        import std.format;

        return format!"struct %s {\n  int %s\n}"(name, member);
    }

    mixin(makeStruct("S", "m")); // ← compilation ERROR
    void main()
    {
    }

При компиляции с ключом -mixin ошибка компиляции будет указывать на
на строку внутри указанного файла (mixed_in_code в примере ниже):

.. code-block:: d

    $ dmd -mixin=mixed_in_code deneme.d
    mixed_in_code(154): Error: semicolon expected, not }

Наряду со всем остальным кодом, который смешивается стандартной библиотекой, в файле mixed_in_code будет находиться
в указанной строке внутри файла mixed_in_code будет следующий код:


.. code-block:: console

    [...]
    // expansion at deneme.d(6)
    struct S
    {
        int m
    }  <- строка 154

Еще одним вариантом отладки строковых миксинов является pragma(msg) (:ref:`Pragma<глава_65_start>`), которая
выводит сгенерированный код во время компиляции. Этот вариант менее практичен
поскольку требует временной замены ключевого слова mixin на pragma(msg)
для отладки:

.. code-block:: d

    pragma(msg, makeStruct("S", "m"));

Пространства имен миксинов
--------------------------

Можно избежать и разрешить неоднозначность имен в шаблонных миксинах.
Например, в следующей программе внутри main() определены две переменные i.
программе: одна определена явно в main, а другая - смешано. Когда
смешиваемое имя совпадает с именем, находящимся в окружающей области видимости, то
используется имя, находящееся в окружающей области видимости:

.. code-block:: d

    import std.stdio;

    template Templ()
    {
        int i;
        void print()
        {
            writeln(i); // Always the 'i' that is defined in Templ
        }
    }

    void main()
    {
        int i;
        mixin Templ;
        i = 42; // Sets the 'i' that is defined explicitly in main
        writeln(i); // Prints the 'i' that is defined explicitly in main
        print(); // Prints the 'i' that is mixed in
    }

Как подразумевалось в комментариях выше, шаблонные миксины определяют пространство имен для своего содержимого
и имена, которые появляются в коде шаблона, сначала ищутся в
этом пространстве имен. Мы можем видеть это в поведении print():

.. code-block:: console

    42
    0 ← printed by print()

Компилятор не может разрешить конфликты имен, если одно и то же имя определено более чем
более чем одним шаблонным миксином. Давайте посмотрим на это в короткой программе, которая смешивает один и тот же
экземпляр шаблона дважды:

.. code-block:: d


    template Templ()
    {
        int i;
    }

    void main()
    {
        mixin Templ;
        mixin Templ;
        i = 42; // ← compilation ERROR
    }

.. code-block:: console

    Error: deneme.main.Templ!().i at ... conflicts with
    deneme.main.Templ!().i at ...

Чтобы предотвратить это, можно назначить идентификаторы пространства имен для шаблонных миксинов
и ссылаться на содержащиеся в них имена по этим идентификаторам:

.. code-block:: d

    mixin Templ A; // Defines A.i
    mixin Templ B; // Defines B.i
    A.i = 42; // ← not ambiguous anymore

mixins строк не имеют таких возможностей пространства имен. Тем не менее, тривиально использовать
строку в качестве шаблона-mixins, просто передав ее через простой шаблон-обертку
шаблона.
Давайте сначала посмотрим на аналогичный конфликт имен со строковыми mixins:

.. code-block:: d

    void main()
    {
        mixin ("int i;");
        mixin ("int i;"); // ← ошибка компиляции
        i = 42;
    }

.. code-block:: console

    Error: declaration deneme.main.i is already defined

Одним из способов решения этой проблемы является передача строки через следующий тривиальный
шаблон, который эффективно преобразует строковый миксин в шаблонный миксин:

.. code-block:: d

    template Templatize(string str)
    {
        mixin(str);
    }

    void main()
    {
        mixin Templatize!("int i;") A; // Defines A.i
        mixin Templatize!("int i;") B; // Defines B.i
        A.i = 42; // ← not ambiguous anymore
    }

Строковые миксины в перегрузке операторов
------------------------------------------

В главе :ref:`Перегрузка операторов<глава_53_start>` мы уже видели, как миксины
помогали в определении некоторых операторов.
На самом деле, причина, по которой большинство функций-членов операторов определяются как
шаблонов, заключается в том, чтобы сделать операторы доступными в виде строковых значений, чтобы их можно было
использовать для генерации кода. Примеры этого мы видели как в этой главе, так и в
решениях упражнений.

Смешанные деструкторы
----------------------

К определенному пользователем типу можно подмешать несколько деструкторов. Эти
деструкторы вызываются в обратном порядке по отношению к операторам миксинов, которые их добавили
их. Эта возможность позволяет добавлять к типу различные ресурсы, каждый из которых
с собственным кодом очистки:


.. code-block:: d

    import std.stdio;

    mixin template Foo()
    {
        ~this()
        {
            writeln("Destructor mixed-in by Foo");
        }
    }

    mixin template Bar()
    {
        ~this()
        {
            writeln("Destructor mixed-in by Bar");
        }
    }

    struct S
    {
        ~this()
        {
            writeln("Actual destructor");
        }

        mixin Foo;
        mixin Bar;
    }

    void main()
    {
        auto s = S();
    }

.. code-block:: console

    Destructor mixed-in by Bar
    Destructor mixed-in by Foo
    Actual destructor

Из-за ошибки, обнаруженной на момент написания этого текста, это поведение не распространяется на другие специальные
функциям, таким как конструкторы. Кроме того, деструктор, подмешанный в строковый миксин
конфликтует с существующим деструктором типа

Импорт текстовых файлов
------------------------

Можно вставлять содержимое текстовых файлов в код во время компиляции. Содержимое
рассматриваются как строковые литералы и могут быть использованы везде, где можно использовать строки. Например,
Например, их можно подмешивать в код.
Например, предположим, что в файловой системе есть два текстовых файла с именами
file_one и file_two со следующим содержимым

* file_one:
  Hello
* file_two:
  s ~= ", World!";
  import std.stdio;
  writeln(s);

Две директивы импорта в следующей программе будут соответствовать
содержимое этих файлов, преобразованное в строковые литералы во время компиляции

.. code-block:: d

    void main()
    {
        string s = import("file_one");
        mixin(import("file_two"));
    }

Импорт текстовых файлов (он же импорт строк) требует ключа компилятора -J, который указывает
компилятору, где найти текстовые файлы. Например, если два файла находятся в папке
текущем каталоге (в средах Linux указывается через .), программу можно
скомпилировать с помощью следующей команды:

.. code-block:: console

    $ dmd -J. deneme.d

Вывод:

.. code-block:: console

    Hello, World!

Если рассматривать содержимое файла как строковые литералы, то эта программа эквивалентна
следующей:

.. code-block:: d

    void main()
    {
        string s = `Hello`; // ← Content of file_one as string
        mixin(`s ~= ", World!";
        import std.stdio;
        writeln(s);`); // ← Content of file_two as string
    }

Далее, учитывая также и смешанную строку, программа эквивалентна
следующей:

.. code-block:: d

    void main()
    {
        string s = `Hello`;
        s ~= ", World!";
        import std.stdio;

        writeln(s);
    }

Пример
------

(Примечание:Указание предикатов в виде строк использовалось чаще всего до появления синтаксиса лямбды
Хотя строковые предикаты, как в этом примере, по-прежнему используются в
Phobos, синтаксис=>лямбда может быть более подходящим в большинстве случаев).
Рассмотрим следующий шаблон функции, которая принимает массив чисел
и возвращает другой массив, состоящий из элементов, удовлетворяющих определенному
условию:

.. code-block:: d

    int[] filter(string predicate)(int[] numbers)
    {
        int[] result;
        foreach (number; numbers)
        {
            if (mixin(predicate))
            {
                result ~= number;
            }
        }
        return result;
    }

Этот шаблон функции принимает условие фильтрации в качестве параметра шаблона
и вставляет это условие непосредственно в оператор if как есть.
Чтобы условие выбирало числа, которые, например, меньше 7, условие if
должно выглядеть следующим образом:


.. code-block:: d

    if (number < 7) {

Пользователи шаблона filter() могут предоставить условие в виде строки:

.. code-block:: d

    int[] numbers = [ 1, 8, 6, -2, 10 ];
    int[] chosen = filter!"number < 7"(numbers);

Важно отметить, что имя, используемое в параметре шаблона, должно совпадать с именем
переменной, используемой в реализации filter(). Таким образом, шаблон должен
документировать, каким должно быть это имя, а пользователи должны использовать это имя.
В Phobos используются имена, состоящие из одних букв, например a, b, n и т. д.
